using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Collections;

namespace TXLooker.Common
{
    public delegate void ExtEventHandler<T>( object sender, T item );
    public delegate bool ConditionChecker<T>( T item );

    [Serializable]
    public class ExtCollectionBase<T> : IList<T>, ICollection<T>, IEnumerableSync<T>, IEnumerable<T>, ICloneable
        where T : class, new()
    {
        #region Internal Types

        /// <summary>
        /// 
        /// </summary>
        internal enum ItemChangedNotification
        {
            Undefined,
            /// <summary>
            /// Type implement interface INotifyObjectChanged
            /// </summary>
            INotifyObjectChanged,
            /// <summary>
            /// Type implement interface INotifyPropertyChanged
            /// </summary>
            INotifyPropertyChanged,
            /// <summary>
            /// Item Type do not implement interface INotifyPropertyChanged, but
            /// properties has '[PropertyName][Changed]' events ( every event is EventHandler type)
            /// </summary>
            PropertyChanged
        }

        /// <summary>
        /// 
        /// </summary>
        [Serializable]
        public sealed class ItemChangeInfo
        {
            #region Class Constructors

            public ItemChangeInfo()
            { }
            public ItemChangeInfo( T item, PropertyChangedEventArgs args )
                : this()
            {
                this.Item = item;
                this.ChangedPropertyEventArgs = args;
            }

            #endregion Class Constructors

            #region Changed Item

            /// <summary>
            /// Changed item in collection
            /// </summary>
            public T Item
            {
                get { return this.m_item; }
                set
                {
                    if( value == null )
                        throw new NullReferenceException();

                    this.m_item = value;
                }
            }
            private T m_item = default( T );

            #endregion Item

            #region PropertyChangedEventArgs

            /// <summary>
            /// Contains information about changed property
            /// This property will not be null only if type T support interface INotifyPropertyChanged,
            /// and null if type properties have changed EventHandler type events as [PropertyName][Changed]
            /// </summary>
            public PropertyChangedEventArgs ChangedPropertyEventArgs
            {
                get { return this.m_propertyEventArgs; }
                set { this.m_propertyEventArgs = value; }
            }
            private PropertyChangedEventArgs m_propertyEventArgs = null;

            #endregion Descriptor
        }

        protected delegate object ActionEventHandler( object[] args );

        #endregion Internal Types

        /// <summary>
        /// main internal data container
        /// </summary>
        protected BindingList<T> m_items = new BindingList<T>();


        /// <summary>
        /// 
        /// </summary>
        [field: NonSerialized]
        private static ItemChangedNotification m_itemChangedType = ItemChangedNotification.Undefined;
        /// <summary>
        /// 
        /// </summary>
        [field: NonSerialized]
        private static List<EventInfo> m_eventInfoList = null;



        #region Class Constructor

        static ExtCollectionBase()
        {
            Type membersType = typeof( T );

            if( membersType.GetInterface( "INotifyObjectChanged" ) != null )
            {
                m_itemChangedType = ItemChangedNotification.INotifyObjectChanged;
            }
            else if( membersType.GetInterface( "INotifyPropertyChanged" ) != null )
            {
                m_itemChangedType = ItemChangedNotification.INotifyPropertyChanged;
            }
            else
            {
                PropertyInfo[] properties = membersType.GetProperties();

                foreach( PropertyInfo property in properties )
                {
                    EventInfo eInfo = membersType.GetEvent( property.Name + "Changed" );

                    if( eInfo == null )
                        continue;

                    if( eInfo.EventHandlerType != typeof( EventHandler ) )
                        continue;

                    if( m_eventInfoList == null )
                        m_eventInfoList = new List<EventInfo>();

                    m_eventInfoList.Add( eInfo );
                }

                if( m_eventInfoList != null )
                    m_itemChangedType = ItemChangedNotification.PropertyChanged;
            }
        }

        public ExtCollectionBase()
        {
            this.m_items.ListChanged += new ListChangedEventHandler( OnListChanged );
        }
        public ExtCollectionBase( IEnumerable<T> items ) : this()
        {
            if( items == null ) throw new ArgumentException();

            foreach( T item in items )
                this.m_items.Add( item );
        }

        #endregion Class Constructor

        #region IList<T> Members

        public virtual void Insert( int index, T item )
        {
            RaisePreAdd( item );

            ActionEventHandler handler =
                delegate( object[] args )
                {
                    this.m_items.Insert( index, item );
                    return null;
                };

            this.ExecuteAction( handler, null );
        }
        public virtual void RemoveAt( int index )
        {
            ActionEventHandler handler =
                delegate( object[] args )
                {
                    T item = this.m_items[index];
                    this.Remove( item );
                    return null;
                };

            this.ExecuteAction( handler, null );
        }
        public virtual T this[int index]
        {
            get
            {
                ActionEventHandler handler = delegate( object[] args ) { return this.m_items[index]; };
                return (T)this.ExecuteAction( handler, null );
            }
            set
            {
                ActionEventHandler handler = delegate( object[] args ) { return this.m_items[index] = value; };
                this.ExecuteAction( handler, null );
            }
        }
        public virtual int IndexOf( T item )
        {
            ActionEventHandler handler = delegate( object[] args ) { return this.m_items.IndexOf( item ); };
            return (int)this.ExecuteAction( handler, null );
        }

        #endregion

        #region ICollection<T> Members

        public virtual void Add( T item )
        {
            RaisePreAdd( item );

            ActionEventHandler handler =
                delegate( object[] args )
                {
                    this.m_items.Add( item );
                    return null;
                };

            this.ExecuteAction( handler, null );
        }
        public virtual void Clear()
        {
            ActionEventHandler handler =
                delegate( object[] args )
                {
                    while( this.Count != 0 )
                        this.Remove( this[0] );

                    return null;
                };

            this.ExecuteAction( handler, null );
        }
        public int Count
        {
            get
            {
                ActionEventHandler handler = delegate { return this.m_items.Count; };
                return (int)this.ExecuteAction( handler, null );
            }
        }
        public bool IsReadOnly
        {
            get { throw new NotImplementedException( "The method 'IsReadOnly' or operation is not implemented." ); }
        }
        public virtual bool Remove( T item )
        {
            RaisePreDelete( item );

            ActionEventHandler handler =
                delegate( object[] args )
                {
                    bool bRemoved = this.m_items.Remove( item );
                    if( bRemoved ) this.UnRegisterItem( item );
                    return bRemoved;
                };

            return (bool)this.ExecuteAction( handler, null );
        }
        public virtual bool Contains( T item )
        {
            ActionEventHandler handler = delegate( object[] args ) { return this.Contains( item ); };
            return (bool)this.ExecuteAction( handler, null );
        }
        public virtual void CopyTo( T[] array, int arrayIndex )
        {
            ActionEventHandler handler =
                delegate( object[] args )
                {
                    this.m_items.CopyTo( array, arrayIndex );
                    return null;
                };
            this.ExecuteAction( handler, null );
        }

        #endregion

        #region explicit ICollection<T> Members

        void ICollection<T>.Add( T item )
        {
            this.Add( item );
        }
        void ICollection<T>.Clear()
        {
            this.Clear();
        }
        bool ICollection<T>.Contains( T item )
        {
            return this.Contains( item );
        }
        void ICollection<T>.CopyTo( T[] array, int arrayIndex )
        {
            this.CopyTo( array, arrayIndex );
        }
        int ICollection<T>.Count
        {
            get { return this.Count; }
        }
        bool ICollection<T>.IsReadOnly
        {
            get { return this.IsReadOnly; }
        }
        bool ICollection<T>.Remove( T item )
        {
            return this.Remove( item );
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return this.m_items.GetEnumerator();
        }

        #endregion

        #region explicit IEnumerable<T> Members

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region explicit IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region IEnumerableSync<T> Members

        object IEnumerableSync<T>.SyncRoot
        {
            get { return this.SyncRoot; }
        }

        #endregion

        #region Synchronization

        /// <summary>
        /// object for cillection synchronization
        /// </summary>
        public object SyncRoot
        {
            get { return this.m_syncRoot; }
        }
        private object m_syncRoot = new object();

        /// <summary>
        /// If 'true' then collection is thread safety, 'false' overwise
        /// </summary>
        public bool IsSynchronized
        {
            get { return this.m_bSynchronized; }
            set { this.m_bSynchronized = value; }
        }
        private bool m_bSynchronized = true;

        #endregion Synchronization

        #region PreAddItem event

        public event ExtEventHandler<T> PreAddItem
        {
            add
            {
                lock( this.m_syncPreAddItem )
                {
                    this.m_preAddItemEH += value;
                }
            }
            remove
            {
                lock( this.m_syncPreAddItem )
                {
                    this.m_preAddItemEH -= value;
                }
            }
        }
        [field: NonSerialized]
        private ExtEventHandler<T> m_preAddItemEH = null;

        private object m_syncPreAddItem = new object();

        private void RaisePreAdd( T item )
        {
            RaiseEventHandler( this.m_preAddItemEH, new object[] { this, item }, this.m_syncPreAddItem );
        }

        #endregion PreAddItem event

        #region AddedItem

        public event ExtEventHandler<T> AddedItem
        {
            add
            {
                lock( this.m_syncAddedItem )
                {
                    this.m_AddedItemEH += value;
                }
            }
            remove
            {
                lock( this.m_syncAddedItem )
                {
                    this.m_AddedItemEH -= value;
                }
            }
        }
        [field: NonSerialized]
        private ExtEventHandler<T> m_AddedItemEH = null;

        private object m_syncAddedItem = new object();

        private void RaiseAddedItem( T item )
        {
            this.RaiseEventHandler( this.m_AddedItemEH, new object[] { this, item }, this.m_syncAddedItem );
        }

        #endregion AddItem

        #region PreDeleteItem event

        public event ExtEventHandler<T> PreDeleteItem
        {
            add
            {
                lock( this.m_syncPreDeleteItem )
                {
                    this.m_preDeleteItemEH += value;
                }
            }
            remove
            {
                lock( this.m_syncPreDeleteItem )
                {
                    this.m_preDeleteItemEH -= value;
                }
            }
        }
        [field: NonSerialized]
        private ExtEventHandler<T> m_preDeleteItemEH = null;

        private object m_syncPreDeleteItem = new object();

        private void RaisePreDelete( T item )
        {
            RaiseEventHandler( this.m_preDeleteItemEH, new object[] { this, item }, this.m_syncPreDeleteItem );
        }

        #endregion PreDeleteItem event

        #region DeleteItem

        public event EventHandler DeletedItem
        {
            add
            {
                lock( this.m_syncDeletedItem )
                {
                    this.m_DeletedItemEH += value;
                }
            }
            remove
            {
                lock( this.m_syncDeletedItem )
                {
                    this.m_DeletedItemEH -= value;
                }
            }
        }
        [field: NonSerialized]
        private EventHandler m_DeletedItemEH = null;

        private object m_syncDeletedItem = new object();

        private void RaiseDeletedItem()
        {
            this.RaiseEventHandler( this.m_DeletedItemEH, new object[] { this, EventArgs.Empty }, this.m_syncAddedItem );
        }

        #endregion AddItem

        #region ItemChanged

        public event ExtEventHandler<ItemChangeInfo> ItemChanged
        {
            add
            {
                lock( this.m_syncItemChanged )
                {
                    this.m_ItemChangedEH += value;
                }
            }
            remove
            {
                lock( this.m_syncItemChanged )
                {
                    this.m_ItemChangedEH -= value;
                }
            }
        }
        [field: NonSerialized]
        private ExtEventHandler<ItemChangeInfo> m_ItemChangedEH = null;

        private object m_syncItemChanged = new object();

        private void RaiseItemChanged( ItemChangeInfo info )
        {
            this.RaiseEventHandler( this.m_ItemChangedEH, new object[] { this, info }, this.m_syncItemChanged );
        }

        #endregion ItemChanged

        /// <summary>
        /// Common Pattern method for raise any event
        /// </summary>
        protected void RaiseEventHandler( Delegate handler, object[] args, object syncObject )
        {
            EventHandler eh = delegate { if( handler != null ) handler.DynamicInvoke( args ); };

            if( syncObject != null )
            {
                lock( syncObject )
                {
                    eh.Invoke( this, EventArgs.Empty );
                }
            }
            else
            {
                eh.Invoke( this, EventArgs.Empty );
            }
        }
        /// <summary>
        /// 
        /// </summary>
        protected object ExecuteAction( ActionEventHandler handler, object[] args )
        {
            if( this.IsSynchronized )
            {
                lock( this.SyncRoot )
                {
                    return handler.DynamicInvoke( new object[] { args } );
                }
            }
            else
            {
                return handler.DynamicInvoke( new object[] { args } );
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        private void RegisterItem( T item )
        {
            switch( m_itemChangedType )
            {
                case ItemChangedNotification.Undefined:
                    break;
                case ItemChangedNotification.INotifyObjectChanged:
                    ( item as INotifyObjectChanged ).ObjectChanged += new EventHandler( OnObjectChanged );
                    break;
                case ItemChangedNotification.INotifyPropertyChanged:
                    ( item as INotifyPropertyChanged ).PropertyChanged += new PropertyChangedEventHandler( OnPropertyChanged );
                    break;
                case ItemChangedNotification.PropertyChanged:
                    {
                        foreach( EventInfo eInfo in m_eventInfoList )
                            eInfo.AddEventHandler( item, new EventHandler( this.OnPropertyChanged ) );
                    }
                    break;
                default:
                    throw new NotImplementedException();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        private void UnRegisterItem( T item )
        {
            switch( m_itemChangedType )
            {
                case ItemChangedNotification.Undefined:
                    break;
                case ItemChangedNotification.INotifyObjectChanged:
                    ( item as INotifyObjectChanged ).ObjectChanged -= new EventHandler( OnObjectChanged );
                    break;
                case ItemChangedNotification.INotifyPropertyChanged:
                    ( item as INotifyPropertyChanged ).PropertyChanged -= new PropertyChangedEventHandler( OnPropertyChanged );
                    break;
                case ItemChangedNotification.PropertyChanged:
                    {
                        foreach( EventInfo eInfo in m_eventInfoList )
                            eInfo.RemoveEventHandler( item, new EventHandler( this.OnPropertyChanged ) );
                    }
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        #region Handlers

        private void OnListChanged( object sender, ListChangedEventArgs args )
        {
            switch( args.ListChangedType )
            {
                case ListChangedType.ItemChanged:
                    //throw new NotImplementedException();
                    break;
                case ListChangedType.ItemAdded:
                    {
                        T item = this[args.NewIndex];
                        this.RegisterItem( item );
                        this.RaiseAddedItem( item );
                    }
                    break;
                case ListChangedType.ItemDeleted:
                    this.RaiseDeletedItem();
                    break;
                default:
                    break;
            }
        }

        private void OnPropertyChanged( object sender, PropertyChangedEventArgs args )
        {
            this.RaiseItemChanged( new ItemChangeInfo( (T)sender, args ) );
        }
        private void OnPropertyChanged( object sender, EventArgs e )
        {
            this.RaiseItemChanged( new ItemChangeInfo( (T)sender, null ) );
        }
        private void OnObjectChanged( object sender, EventArgs args )
        {
            this.RaiseItemChanged( new ItemChangeInfo( (T)sender, null ) );
        }

        #endregion Handlers

        /// <summary>
        /// 
        /// </summary>
        public void AddRange( IEnumerable<T> items )
        {
            foreach( T item in items )
                this.Add( item );
        }
        /// <summary>
        /// 
        /// </summary>
        public T FindFirst( ConditionChecker<T> checker )
        {
            ActionEventHandler handler =
                delegate
                {
                    foreach( T item in this.m_items )
                        if( checker( item ) ) return item;

                    return null;
                };
            return (T)this.ExecuteAction( handler, null );

        }
        /// <summary>
        /// 
        /// </summary>
        public IEnumerable<T> FindAll( ConditionChecker<T> checker )
        {
            ActionEventHandler handler =
                delegate
                {
                    List<T> result = new List<T>();

                    foreach( T item in this.m_items )
                        if( checker( item ) ) result.Add( item );

                    return result;
                };
            return (IEnumerable<T>)this.ExecuteAction( handler, null );
        }

        #region ICloneable Members

        public virtual object Clone()
        {
            ActionEventHandler handler =
                delegate
                {
                    ExtCollectionBase<T> clone = (ExtCollectionBase<T>)Activator.CreateInstance( this.GetType() );

                    foreach( T item in this.m_items )
                        clone.m_items.Add( item );

                    return clone;
                };
            return this.ExecuteAction( handler, null );
        }

        public virtual object DeepClone()
        {
            if( typeof( T ).GetInterface( "ICloneable" ) == null )
                throw new Exception();

            ActionEventHandler handler =
                delegate
                {
                    ExtCollectionBase<T> clone = (ExtCollectionBase<T>)Activator.CreateInstance( this.GetType() );

                    foreach( T item in this.m_items )
                        clone.m_items.Add( (T)( item as ICloneable ).Clone() );

                    return clone;
                };
            return this.ExecuteAction( handler, null );
        }

        #endregion
    }
}
